# This file is part of the AutoMAT distribution (https://bitbucket.com/mahomaho/AutoMAT).
# Copyright (c) 2019 Mattias Holmqvist.
# 
# AutoMAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
# AutoMAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with AutoMAT.  If not, see <https://www.gnu.org/licenses/>.

from AutoMAT import *

class RteList (list):
	def __init__(self,*args):
		list.__init__(self, *args)
		if self.__len__() > 0:
			first=self.__iter__().__next__()
			if hasattr(first,'__iter__') and not isinstance(first, str):
				ret=list(e for i in self for e in i)
				self.clear()
				self.extend(ret)
	
	def append(self, *args, **kwargs):
		list.append(self, *args, **kwargs)
		return self

	def sort(self,key=None, reverse=False):
		list.sort(self,key=key,reverse=reverse)
		return self
		
	def __add__(self,other):
		res=RteList(self)
		res.extend(other)
		return res
	def __iadd__(self,other):
		return NotImplemented
	def __isub__(self,other):
		return NotImplemented
	def __getitem__(self, n):
		if isinstance(n,slice):
			return RteList(super().__getitem__(n))
		if isinstance(n,int) and n < len(self):
			return super().__getitem__(n)
		return ModelNone
	def __setattr__(self, name, value):
		for val in self:
			setattr(val,name,value)
	def __getattr__(self, name):
		if len(self) == 0:
			return RteList()
		try:
			attr = getattr(self[0],name)
			if not isinstance(attr, RteList) and callable(attr):
				def on_all(*args, **kwargs):
					ret = RteList()
					for obj in self:
						try:
							res = getattr(obj, name)(*args, **kwargs)
							if hasattr(res,'__iter__') and not isinstance(res, str):
								ret.extend(res)
							else:
								ret.append(res)
						except: pass
					return ret
				return on_all
		except:pass
		ret = RteList()
		for x in self:
			try:
				res = getattr(x,name)
				if hasattr(res,'__iter__') and not isinstance(res, str):
					ret.extend(res)
				else: ret.append(res)
			except: pass
		return ret

	def __dir__(self):
		ret = list(RteList.__dict__.keys())
		for x in self:
			ret.extend(dir(x))
		return list(set(ret))

def RunnableApiOpen(runnables):
	res = '#if defined(RTE_RUNNABLEAPI)'
	for runnable in runnables:
		res += f' || \\\n\tdefined(RTE_RUNNABLEAPI_{runnable.shortName.val()})'
	return res+'\n'
def RunnableApiClose():
	return '#endif // RTE_RUNNABLEAPI\n'

def TYPE(_type, internalBehavior=None):
	if isinstance(_type,autosar_r4p0.ModeDeclarationGroup):
		return _VALUE_TYPE(_type, internalBehavior)
	elif isinstance(_type,autosar_r4p0.SwBaseType):
		return _BASE_TYPE(_type,internalBehavior)
	type_ref=_type
	while type_ref.category.val() == 'TYPE_REFERENCE':
		type_ref = type_ref.swDataDefProps.implementationDataType.ref()
	category=type_ref.category.val()
	if category in ('VALUE','BOOLEAN'):
		return _VALUE_TYPE(_type, internalBehavior)
	elif category=='ARRAY':
		return _ARRAY_TYPE(_type, internalBehavior)
	elif category in ('COM_AXIS','CURVE','MAP'):
		return _CALIB_ARRAY_TYPE(_type, internalBehavior)
	elif category == 'DATA_REFERENCE':
		return _PTR_TYPE(_type, internalBehavior)
	else:
		print(f'Error: unsupported type {_type.shortName.val()}')


class _TYPE():
	_impl_type=None
	_type=None
	_compu=None
	_type_base=None
	#_int_beh=None
	def name(self):
		return self._type.shortName.val()
	def impl_name(self):
		return self._impl_type.shortName.val()
	def isArray(self):
		return False
	def isRecord(self):
		return False
	def isPrimitive(self):
		return False
	def isAtomic(self):
		return False
	def isEnum(self):
		return self._compu and self._compu.category.val()=='TEXTTABLE'
	def _scale_val(self,val):
		if self._compu:
			if self.isEnum():
				if isinstance(val, str):
					val=self._texttranslate[val]
				else:
					if self.base_isFloat():
						val=float(val)
					else:
						val=int(val)
			elif self._compu.category.val() == 'LINEAR':
				val=float(val)
				toF = float(default(1,self)._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v[1].val()) / float(default(1,self)._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v[0].val())
				toO=float(default(0,self)._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v[0].val()) / float(default(1,self)._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v[0].val())
				val=(val+toO)/toF
				if not self.base_isFloat():
					val=round(val)
		return val
	def base_isSigned(self):
		return self._type_base.baseTypeEncoding.val() in ('2C','IEEE754')
	def base_isFloat(self):
		return self._type_base.baseTypeEncoding.val() == 'IEEE754'
	def base_isBoolean(self):
		return self._type_base.baseTypeEncoding.val() == 'BOOLEAN'
	def base_size(self):
		return int(self._type_base.baseTypeSize.val())

class _VALUE_TYPE(_TYPE):
	_texttranslate={}
	def __init__(self,type_ref, internalBehavior=None):
		self._type=type_ref
		# internalBehaviour only needed for application data types

		if isinstance(type_ref,autosar_r4p0.ApplicationPrimitiveDataType):
			self._compu=type_ref.swDataDefProps.compuMethod.ref()
			if internalBehavior is None:
				assert internalBehavior is not None
			type_ref=internalBehavior.dataTypeMapping.ref().dataTypeMap.select.findfirst(lambda e: e.applicationDataType.ref() == type_ref).implementationDataType.ref()
		elif isinstance(type_ref,autosar_r4p0.ModeDeclarationGroup):
			assert internalBehavior is not None
			type_ref=internalBehavior.dataTypeMapping.ref().modeRequestTypeMap.select.findfirst(lambda e: e.modeGroup.ref() == type_ref).implementationDataType.ref()
		else:
			# ImplementationDataType
			self._compu=type_ref.swDataDefProps.compuMethod.ref()
		#if type_ref.category.val() == 'ARRAY' and 
		self._impl_type=type_ref
		while type_ref.category.val() == 'TYPE_REFERENCE':
			type_ref = type_ref.swDataDefProps.implementationDataType.ref()
		self._type_base=type_ref.swDataDefProps.baseType.ref()
		if self._compu:
			if self.isEnum():
				for scale in self._compu.compuInternalToPhys.compuScale:
					if scale.symbol!=None:
						name = scale.symbol.val()
					elif scale.compuConst.vt!=None:
						name = scale.compuConst.vt.val()
					elif scale.shortLabel!=None:
						name = scale.shortLabel.val()
					else:
						raise AssertionError('compu error')
					if self.base_isFloat():
						val=float(scale.lowerLimit.val())
					else:
						val=int(scale.lowerLimit.val())
					self._texttranslate[name]=val
					
		#self._int_beh=internalBehavior
	def isPrimitive(self):
		return True
	def isAtomic(self):
		return int(self._type_base.baseTypeSize.val()) <= 64
	def print_val(self,val):
		if self.base_isFloat():
			return str(float(val))
		elif self.base_isSigned():
			return str(val.__round__())
		elif self.base_isBoolean():
			return 'TRUE' if val else 'FALSE'
		elif self.isEnum():
			return str(val)
		else:
			return str(val.__round__())+'U'
		
	def VALUE2str(self,VALUE_SPEC):
		# is this correct with automat 2.0?
		while isinstance(VALUE_SPEC,autosar_r4p0.ConstantReference):
			VALUE_SPEC=VALUE_SPEC.constant.ref().valueSpec[0]
		if isinstance(VALUE_SPEC,autosar_r4p0.ApplicationValueSpecification):
			if VALUE_SPEC.swValueCont.swValuesPhys.vt:
				val=self._scale_val(VALUE_SPEC.swValueCont.swValuesPhys.vt[0].val())
			else:
				val=self._scale_val(VALUE_SPEC.swValueCont.swValuesPhys.v[0].val())
		elif isinstance(VALUE_SPEC,autosar_r4p0.NumericalValueSpecification):
			val=VALUE_SPEC.value.val()
		else:
			val=0.0
		return self.print_val(val)
	def isLinear(self):
		if not self._compu:
			return True
		if self._compu.category.val() == 'LINEAR':
			return True
		if self._compu.category.val() == 'RATIONAL':
			if(self._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v.__len__() <= 2 and
			   self._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v.__len__() <= 1):
				return True
		return False
			
	def ScalingNeeded(self,fromT):
		if type(fromT) is not type(self):
			print(f'Error: trying to map datatype {self.name()} to datatype {fromT.name()}')
			return False
		if fromT.isLinear() and self.isLinear():
			fromCoffs=default(None,fromT)._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs
			toCoffs=default(None,self)._compu.compuInternalToPhys.compuScale[0].compuRationalCoeffs
			if(default(0,fromCoffs).compuNumerator.v[0].val() != default(0,toCoffs).compuNumerator.v[0].val() or
			   default(1,fromCoffs).compuNumerator.v[1].val() != default(1,toCoffs).compuNumerator.v[1].val() or
			   default(1,fromCoffs).compuDenominator.v[0].val() != default(1,toCoffs).compuDenominator.v[0].val()):
				return True
			else:
				return False
		else:
			#todo
			return False
	def copy(self, tovar, fromT, var):
		return f'{tovar} = {self.Scale(fromT,var)};\n'
	def Scale(self,fromT, var):
		toT = self
		fromC=fromT._compu
		toC=toT._compu
		res=f'({toT.impl_name()})('
		if fromT.isLinear() and toT.isLinear():
			fromF = float(default(1,fromC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v[1].val()) / float(default(1,fromC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v[0].val())
			toF = float(default(1,toC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v[1].val()) / float(default(1,toC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v[0].val())
			fromO=float(default(0,fromC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v[0].val()) / float(default(1,fromC).fromC.compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v[0].val())
			toO=float(default(0,toC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuNumerator.v[0].val()) / float(default(1,toC).compuInternalToPhys.compuScale[0].compuRationalCoeffs.compuDenominator.v[0].val())
			offset = (fromO-toO)/toF
			if fromT.base_isFloat() or toT.base_isFloat():
				if fromF != toF:
					res+=f'{fromF/toF} * (float){var}'
				else:
					res+=f'(float){var}'
				if offset < 0:
					res+=f' - {-offset}'
				elif offset > 0:
					res+=f' + {offset}'
			else:
				from fractions import Fraction
				maxsize = max(fromT.base_size(),toT.base_size())
				fr = Fraction.from_float(toF/fromF).limit_denominator(2**maxsize)
				offset = float(offset*fr.numerator).__round__()
				if offset != 0 and fr.numerator != 1:
					res += '('
				if fr.denominator == 1:
					res+=f'{var}'
				else:
					import math
					numsize=math.ceil(math.log2(fr.denominator))
					temptype = 'sint' if toT.base_isSigned() else 'uint'
					temptype+=f'{2 ** math.ceil(math.log2(fromT.base_size()+numsize))}'
					res+=f'({temptype}){var} * {fr.denominator}'
				if offset < 0:
					res += f' - {-offset}'
				elif offset > 0:
					res += f' + {offset}'
				if fr.numerator != 1:
					if offset != 0:
						res+=')'
					res += f' / {fr.numerator}'
		else:
			res+=var
		res+=')'
		return res

class _CALIB_ARRAY_TYPE(_TYPE):
	_elementType=None
	_size=0
	def __init__(self,type_ref, internalBehavior=None):
		self._type=type_ref
		# internalBehaviour only needed for application data types
		assert isinstance(type_ref,autosar_r4p0.ApplicationPrimitiveDataType),f'Only primitive appl types may have a category {type_ref.category.val()}'
		if type_ref.category.val()=='COM_AXIS':
			# todo: handle axisGrouped
			self._compu=type_ref.swDataDefProps.swCalprmAxisSet.swCalprmAxis[0].swAxisIndividual.compuMethod.ref()
		elif type_ref.category.val()=='CURVE':
			self._compu=type_ref.swDataDefProps.compuMethod.ref()
		elif type_ref.category.val()=='MAP':
			self._compu=type_ref.swDataDefProps.compuMethod.ref()
		assert internalBehavior is not None
		type_ref=internalBehavior.dataTypeMapping.ref().dataTypeMap.select.findfirst(lambda self: self.applicationDataType.ref() == type_ref).implementationDataType.ref()
		#if type_ref.category.val() == 'ARRAY' and 
		self._impl_type=type_ref
		while type_ref.category.val()=='TYPE_REFERENCE':
			type_ref = type_ref.swDataDefProps.implementationDataType.ref()
		assert type_ref.category.val()=='ARRAY'
		self._size=int(type_ref.subElement[0].arraySize.val())
		self._elementType=TYPE(type_ref.subElement[0].swDataDefProps.implementationDataType.ref())
		self._elementType._compu=self._compu
		self._type_base=self._elementType._type_base
	def isArray(self):
		return True
	def element_type(self):
		return self._elementType
	def VALUE2str(self,VALUE_SPEC):
		while isinstance(VALUE_SPEC,autosar_r4p0.ConstantReference):
			VALUE_SPEC=VALUE_SPEC.constant.ref().valueSpec[0]
		vals=[]
		if isinstance(VALUE_SPEC,autosar_r4p0.ApplicationValueSpecification):
			for physval in VALUE_SPEC.swValueCont.swValuesPhys.children():
				if isinstance(physval, autosar_r4p0.VerbatimString):
					vals.append(val.val())
				elif isinstance(physval, autosar_r4p0.Numerical):
					vals.append(str(self._scale_val(physval.val())))
				else:
					raise AssertionError('Unsupported data element type')
		elif isinstance(VALUE_SPEC,autosar_r4p0.NumericalValueSpecification):
			val=VALUE_SPEC.value.val()
		else:
			raise AssertionError('Unsupported data type')
		return f'{{{", ".join(vals)}}}'

class _PTR_TYPE(_VALUE_TYPE):
	# todo: implement full support for ptr elements
	_ptr_type=None
	def __init__(self,type_ref, internalBehavior=None):
		swdatadefprops=type_ref.swDataDefProps.swPointerTargetProps
		if swdatadefprops.targetCategory.val() in ('VALUE','BOOLEAN'):
			self._ptr_type=TYPE(swdatadefprops.swDataDefProps.baseType.ref(),internalBehavior)
		elif swdatadefprops.targetCategory.val()=='TYPE_REFERENCE':
			self._ptr_type=TYPE(swdatadefprops.swDataDefProps.implementationDataType.ref(),internalBehavior)
		else:
			print('Error: Not supported pointer ref element type')
		self._type=type_ref
		self._impl_type=type_ref

class _BASE_TYPE(_VALUE_TYPE):
	def __init__(self,type_ref, internalBehavior=None):
		self._type=type_ref
		# internalBehaviour only needed for application data types
		self._type_base=type_ref
		self._impl_type=type_ref
	

class _ARRAY_TYPE(_TYPE):
	_elementType=None
	_size=0
	def __init__(self,type_ref, internalBehavior):
		self._type=type_ref
		# internalBehaviour only needed for application data types
		if isinstance(type_ref,autosar_r4p0.ApplicationArrayDataType):
			#self._compu=type_ref.element.type.ref().swDataDefProps.compuMethod.ref()
			self._elementType=TYPE(type_ref.element.type.ref(),internalBehavior)
			self._size=int(type_ref.element.maxNumberOfElements.val())
			if internalBehavior is not None:
				self._impl_type=internalBehavior.dataTypeMapping.ref().dataTypeMap.select.findfirst(lambda e: e.applicationDataType.ref() == type_ref).implementationDataType.ref()
		elif isinstance(type_ref,autosar_r4p0.ApplicationPrimitiveDataType):
			assert 0, "shouldn't end up here"
			#self._compu=type_ref.element.type.ref().swDataDefProps.compuMethod.ref()
			self._elementType=TYPE(type_ref.element.type.ref(),internalBehavior)
			self._size=int(type_ref.element.maxNumberOfElements.val())
			if internalBehavior is not None:
				self._impl_type=internalBehavior.dataTypeMapping.ref().dataTypeMap.select.findfirst(lambda e: e.applicationDataType.ref() == type_ref).implementationDataType.ref()
		else:
			self._impl_type=type_ref
			while type_ref.category.val() == 'TYPE_REFERENCE':
				type_ref = type_ref.swDataDefProps.implementationDataType.ref()
			self._size=int(type_ref.subElement[0].arraySize.val())
			self._elementType=TYPE(type_ref.subElement[0].swDataDefProps.implementationDataType.ref())
		#self._type_base=type_ref.swDataDefProps.baseType.ref()
		#self._int_beh=internalBehavior
	def isArray(self):
		return True
	def element_type(self):
		return self._elementType
		
	def VALUE2str(self,VALUE_SPEC):
		while isinstance(VALUE_SPEC,autosar_r4p0.ConstantReference):
			VALUE_SPEC=VALUE_SPEC.constant.ref().valueSpec[0]
		vals=[]
		if isinstance(VALUE_SPEC, autosar_r4p0.ArrayValueSpecification):
			for val in VALUE_SPEC.element:
				val=self._elementType.VALUE2str(val) #todo: instead of looping over num vals, handle each value as a value and convert it like that
				vals.append(val)
		return f'{{{", ".join(vals)}}}'
			
	def ScalingNeeded(self,fromT):
		if type(fromT) is not type(self):
			print(f'Error: trying to map datatype {self.shortName.val()} to datatype {fromT.shortName.val()}')
			return False
		return self._elementType.ScalingNeeded(fromT)
		
	def copy(self, tovar, fromT, var):
		if type(fromT) is not type(self):
			return f'Error: Trying to copy non array type {fromT.shortName.val()} to array type {self.shortName.val()}'
		res=f'''for(int Rte_i = 0; Rte_i < {self._size}; Rte_i++) {{
			{tovar}[Rte_i] = {self._elementType.Scale(fromT._elementType,var+"[Rte_i]")};
		}}
		'''
		return res
	
def ReferencedObjects(obj):
	import AutoMAT
	def CreateRefs(obj, reflist):
		reflist[obj.type_name()].append(obj)
		if type(obj) is AutoMAT._model_ref:
			CreateRefs(obj.ref(), reflist)
		else:
			for sibling in obj.siblings():
				CreateRefs(sibling,reflist)
	reflist = defaultdict(RteList)
	CreateRefs(obj,reflist)
	return reflist

class OsTask():
	prio=0
	_task=None
	_tasks={}
	def __init__(self, task):
		self._task=task
		self.prio=task.OsTaskPriority.val()
	@staticmethod
	def get(task):
		if task in OsTask._tasks:
			return OsTask._tasks[task]
		else:
			t=OsTask(task)
			OsTask._tasks[task]=t
			return t
			
		
		